package com.nulldev.util.io;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;

public class ByteArrayInOutStream extends ByteArrayOutputStream {
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

	/**
	 * Creates a new ByteArrayInOutStream. The buffer capacity is initially 32
	 * bytes, though its size increases if necessary.
	 */
	public ByteArrayInOutStream() {
		super();
	}

	/**
	 * Creates a new ByteArrayInOutStream, with a buffer capacity of the specified
	 * size, in bytes.
	 *
	 * @param size the initial size.
	 * @exception IllegalArgumentException if size is negative.
	 */
	public ByteArrayInOutStream(int size) {
		super(size);
	}

	@Override
	public void write(final int b) {
		if (!this.wrt) {
			this.wrt = true;
		}
		if (this.dy != null) {
			this.dy.write(b);
		} else {
			ensureCapacity(count + 1);
			System.arraycopy(new byte[]
				{ (byte) b }, 0, buf, count, 1);
			count += 1;
		}
	}

	@Override
	public void write(final byte[] data) throws IOException {
		this.write(data, 0, data.length);
	}

	@Override
	public void write(byte b[], int off, int len) {
		if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) - b.length > 0)) {
			throw new IndexOutOfBoundsException();
		}
		if (!this.wrt) {
			this.wrt = true;
		}
		if (this.dy != null) {
			this.dy.write(b);
		}
		if (b != null && buf != null) {
			ensureCapacity(count + len);
			System.arraycopy(b, off, buf, count, len);
			count += len;
		}
	}

	private void ensureCapacity(int minCapacity) {
		// overflow-conscious code
		if (this.buf != null) {
			if (minCapacity - buf.length > 0)
				grow(minCapacity);
		}
	}

	private void grow(int minCapacity) {
		// overflow-conscious code
		int oldCapacity = buf.length;
		int newCapacity = oldCapacity << 1;
		if (newCapacity - minCapacity < 0)
			newCapacity = minCapacity;
		if (newCapacity - MAX_ARRAY_SIZE > 0)
			newCapacity = hugeCapacity(minCapacity);
		buf = Arrays.copyOf(buf, newCapacity);
	}

	private static int hugeCapacity(int minCapacity) {
		if (minCapacity < 0) // overflow
			throw new OutOfMemoryError();
		return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
	}

	/**
	 * Creates a new ByteArrayInputStream that uses the internal byte array buffer
	 * of this ByteArrayInOutStream instance as its buffer array. The initial value
	 * of pos is set to zero and the initial value of count is the number of bytes
	 * that can be read from the byte array. The buffer array is not copied. This
	 * instance of ByteArrayInOutStream can not be used anymore after calling this
	 * method.
	 * 
	 * @return the ByteArrayInputStream instance
	 */
	public ByteArrayInputStream getInputStream() {
		// create new ByteArrayInputStream that respects the current count
		ByteArrayInputStream in = new ByteArrayInputStream(this.buf, 0, this.count);

		// set the buffer of the ByteArrayOutputStream
		// to null so it can't be altered anymore
		this.buf = null;

		return in;
	}

	private volatile DynamicByteArrayInputStream dy;

	public DynamicByteArrayInputStream getDInputStream() {
		if (this.dy == null) {
			this.dy = new DynamicByteArrayInputStream(this.buf);
		}
		return this.dy;
	}

	private volatile boolean wrt;

	public boolean writtenTo() {
		return this.wrt;
	}

	public boolean hasData() {
		if (this.dy != null) {
			return this.dy.hasData();
		}
		return this.buf.length > 0;
	}

	public boolean isClosed() {
		return this.closed;
	}

	private boolean closed;

	@Override
	public void close() throws IOException {
		super.close();
		this.closed = true;
	}
}
